#pragma once

#include "updater_common.h"

namespace mdtx::online::updater
{

    // MA updates have an additional roll() function that might be faster than call insert() + remove() respectively

    // moving sum
    template <bool kahan>
    struct sum_updater
    {
        double s;
        accumulator<kahan> acc_s;

        sum_updater()
            : s(0.0)
        {
        }

        void insert(double x)
        {
            acc_s(s, x);
        }

        void remove(double x)
        {
            acc_s(s, -x);
        }

        void roll(double x, double y)
        {
            acc_s(s, x - y);
        }
    };

    // simple moving average
    template <bool kahan>
    struct sma_updater
    {
        size_t n;
        double m;
        accumulator<kahan> acc_m;

        sma_updater()
            : n(0),
              m(0.0)
        {
        }

        void insert(double x)
        {
            double d = x - m;
            n += 1;
            acc_m(m, d / n);
        }

        void remove(double x)
        {
            double d = m - x;
            n -= 1;
            acc_m(m, d / n);
        }

        void roll(double x, double y)
        {
            double d = x - y;
            acc_m(m, d / n);
        }
    };

    // weighted moving average
    template <bool kahan>
    struct wma_updater
    {
        size_t n, w;
        double xsum, wsum;
        accumulator<kahan> acc_x, acc_w;

        wma_updater()
            : n(0),
              w(0),
              xsum(0.0),
              wsum(0.0)
        {
        }

        void insert(double x)
        {
            n += 1;
            w += n;
            acc_w(wsum, x * n);
            acc_x(xsum, x);
        }

        void remove(double x)
        {
            w -= n;
            n -= 1;
            acc_w(wsum, -xsum);
            acc_x(xsum, -x);
        }

        void roll(double x, double y)
        {
            acc_w(wsum, x * n - xsum);
            acc_x(xsum, x - y);
        }
    };

    // weighted SMA
    template <bool kahan>
    struct wsma_updater
    {
        double m, wsum;
        accumulator<kahan> acc_m, acc_w;

        wsma_updater()
            : m(0.0),
              wsum(0.0)
        {
        }

        void insert(double x, double w)
        {
            double d = x - m;
            acc_w(wsum, w);
            acc_m(m, (w / wsum) * d);
        }

        void remove(double x, double w)
        {
            double d = m - x;
            acc_w(wsum, -w);
            acc_m(m, (w / wsum) * d);
        }

        void roll(double x, double wx, double y, double wy)
        {
            insert(x, wx);
            remove(y, wy);
        }
    };

    // exponential moving average
    template <bool kahan>
    struct ema_updater
    {
        bool init;
        double a, m;
        accumulator<kahan> acc_m;

        explicit ema_updater(double alpha)
            : init(false),
              a(alpha),
              m(0.0)
        {
        }

        void insert(double x)
        {
            if (init)
            {
                acc_m(m, (x - m) * a);
            }
            else
            {
                m = x;
                init = true;
            }
        }

        void remove(double x)
        {
            x = x;
        }

        void roll(double x, double y)
        {
            insert(x);
            y = y;
        }
    };

    template <size_t order, bool kahan>
    struct xema_updater
    {
        bool init;
        double a;
        std::array<double, order> m;
        std::array<accumulator<kahan>, order> acc_m;

        explicit xema_updater(double alpha)
            : init(false),
              a(alpha)
        {
            m.fill(0.0);
        }

        void insert(double x)
        {
            if (init)
            {
                acc_m[0](m[0], (x - m[0]) * a);
                for (size_t i = 1; i < order; ++i)
                {
                    acc_m[i](m[i], (m[i - 1] - m[i]) * a);
                }
            }
            else
            {
                m.fill(x);
                init = true;
            }
        }

        void remove(double x)
        {
            x = x;
        }

        void roll(double x, double y)
        {
            insert(x);
            y = y;
        }
    };

}
